home *** CD-ROM | disk | FTP | other *** search
/ Pascal Super Library / Pascal Super Library (CW International)(1997).bin / TURB_VIS / TVHC11A / TVHC.PAS < prev    next >
Pascal/Delphi Source File  |  1993-12-26  |  35KB  |  1,177 lines

  1. (***************************************************************************
  2.   TVHC 1.1a
  3.   Improved TVHC, help compiler for Turbo Vision
  4.   PJB December 26, 1993, Internet mail to d91-pbr@nada.kth.se
  5.   Free patches, use at your own risk. All warranties void.
  6.   If even more modified, please state so if you pass this around.
  7.  
  8.   BUGS fixed:
  9.     Reports the .TXT file name in error messages ("fix file name")
  10.     The next file name to be opened or created is kept in a global
  11.     variable, so the error method can find it ("fix file error")
  12.  
  13.   Improvements:
  14.     Reports the actual line numbers of all lines containing unresolved
  15.     help references ("fix unresolved msg") and both actual line numbers
  16.     for redefined help topics ("fix redefinition msg")
  17.  
  18.   The documentation lacks to mention that a line that begins with a
  19.   semi-colon is skipped.
  20.  
  21.   This is a minimally modified version manually created from Borland's
  22.   TVHC using FC output comparing TVHCHack to TVHC.
  23.  
  24. ***************************************************************************)
  25.  
  26. {************************************************}
  27. {                                                }
  28. {   Turbo Vision Demo                            }
  29. {   Copyright (c) 1992 by Borland International  }
  30. {                                                }
  31. {************************************************}
  32.  
  33. {===== TVHC version 1.1 ================================================}
  34. {  Turbo Vision help file compiler documentation.                       }
  35. {=======================================================================}
  36. {                                                                       }
  37. {    Refer to DEMOHELP.TXT for an example of a help source file.        }
  38. {                                                                       }
  39. {    This program takes a help script and produces a help file (.HLP)   }
  40. {    and a help context file (.PAS).  The format for the help file is   }
  41. {    very simple.  Each context is given a symbolic name (i.e FileOpen) }
  42. {    which is then put in the context file (i.e. hcFileOpen).  The text }
  43. {    following the topic line is put into the help file.  Since the     }
  44. {    help file can be resized, some of the text will need to be wrapped }
  45. {    to fit into the window.  If a line of text is flush left with      }
  46. {    no preceeding white space, the line will be wrapped.  All adjacent }
  47. {    wrappable lines are wrapped as a paragraph.  If a line begins with }
  48. {    a space it will not be wrapped. For example, the following is a    }
  49. {    help topic for a File|Open menu item.                              }
  50. {                                                                       }
  51. {       |.topic FileOpen                                                }
  52. {       |  File|Open                                                    }
  53. {       |  ---------                                                    }
  54. {       |This menu item will bring up a dialog...                       }
  55. {                                                                       }
  56. {    The "File|Open" will not be wrapped with the "----" line since     }
  57. {    they both begin with a space, but the "This menu..." line will     }
  58. {    be wrapped.                                                        }
  59. {      The syntax for a ".topic" line is:                               }
  60. {                                                                       }
  61. {        .topic symbol[=number][, symbol[=number][...]]                 }
  62. {                                                                       }
  63. {    Note a topic can have multiple symbols that define it so that one  }
  64. {    topic can be used by multiple contexts.  The number is optional    }
  65. {    and will be the value of the hcXXX context in the context file     }
  66. {    Once a number is assigned all following topic symbols will be      }
  67. {    assigned numbers in sequence.  For example,                        }
  68. {                                                                       }
  69. {       .topic FileOpen=3, OpenFile, FFileOpen                          }
  70. {                                                                       }
  71. {    will produce the follwing help context number definitions,         }
  72. {                                                                       }
  73. {        hcFileOpen  = 3;                                               }
  74. {        hcOpenFile  = 4;                                               }
  75. {        hcFFileOpen = 5;                                               }
  76. {                                                                       }
  77. {    Cross references can be imbedded in the text of a help topic which }
  78. {    allows the user to quickly access related topics.  The format for  }
  79. {    a cross reference is as follows,                                   }
  80. {                                                                       }
  81. (*        {text[:alias]}                                               *)
  82. {                                                                       }
  83. {    The text in the brackets is highlighted by the help viewer.  This  }
  84. {    text can be selected by the user and will take the user to the     }
  85. {    topic by the name of the text.  Sometimes the text will not be     }
  86. {    the same as a topic symbol.  In this case you can use the optional }
  87. {    alias syntax.  The symbol you wish to use is placed after the text }
  88. {    after a ':'. The following is a paragraph of text using cross      }
  89. {    references,                                                        }
  90. {                                                                       }
  91. (*      |The {file open dialog:FileOpen} allows you specify which      *)
  92. {       |file you wish to view.  If it also allow you to navigate       }
  93. {       |directories.  To change to a given directory use the           }
  94. (*      |{change directory dialog:ChDir}.                              *)
  95. {                                                                       }
  96. {    The user can tab or use the mouse to select more information about }
  97. {    the "file open dialog" or the "change directory dialog". The help  }
  98. {    compiler handles forward references so a topic need not be defined }
  99. {    before it is referenced.  If a topic is referenced but not         }
  100. {    defined, the compiler will give a warning but will still create a  }
  101. {    useable help file.  If the undefined reference is used, a message  }
  102. {    ("No help available...") will appear in the help window.           }
  103. {=======================================================================}
  104.  
  105. program TVHC;
  106.  
  107. {$S-}
  108.  
  109. {$M 8192,8192,655360}
  110.  
  111. uses Drivers, Objects, Dos, Strings, HelpFile;
  112.  
  113. { If you get a FILE NOT FOUND error when compiling this program
  114.   from a DOS IDE, change to the \BP\EXAMPLES\DOS\TVDEMO directory
  115.   (use File|Change dir).
  116.  
  117.   This will enable the compiler to find all of the units used by
  118.   this program.
  119. }
  120.  
  121. {======================= File Management ===============================}
  122.  
  123. procedure Error(Text: String); forward;
  124.  
  125. type
  126.   PProtectedStream = ^TProtectedStream;
  127.   TProtectedStream = object(TBufStream)
  128.     FileName: FNameStr;
  129.     Mode: Word;
  130.     constructor Init(AFileName: FNameStr; AMode, Size: Word);
  131.     destructor Done; virtual;
  132.     procedure Error(Code, Info: Integer); virtual;
  133.   end;
  134.  
  135. var
  136.   TextStrm,
  137.   SymbStrm: TProtectedStream;
  138.   ErrorFileName : String;                       { fix file error }
  139.  
  140. const
  141.   HelpStrm: PProtectedStream = nil;
  142.  
  143. constructor TProtectedStream.Init(AFileName: FNameStr; AMode, Size: Word);
  144. begin
  145.   ErrorFileName := AFileName;                   { fix file error }
  146.   inherited Init(AFileName, AMode, Size);
  147.   FileName := AFileName;
  148.   Mode := AMode;
  149. end;
  150.  
  151. destructor TProtectedStream.Done;
  152. var
  153.   F: File;
  154. begin
  155.   inherited Done;
  156.   if (Mode = stCreate) and ((Status <> stOk) or (ExitCode <> 0)) then
  157.   begin
  158.     Assign(F, FileName);
  159.     Erase(F);
  160.   end;
  161. end;
  162.  
  163. procedure TProtectedStream.Error(Code, Info: Integer);
  164. begin
  165.   case Code of
  166.     stError:
  167.       TVHC.Error('Error encountered in file ' + FileName);
  168.     stInitError:
  169.       if Mode = stCreate then
  170.         TVHC.Error('Could not create ' + ErrorFileName)  { fix file error }
  171.       else
  172.         TVHC.Error('Could not find ' + ErrorFileName);   { fix file error }
  173.     stReadError: Status := Code; {EOF is "ok"}
  174.     stWriteError:
  175.       TVHC.Error('Disk full encountered writing file '+ FileName);
  176.   else
  177.       TVHC.Error('Internal error.');
  178.   end;
  179. end;
  180.  
  181. {----- UpStr(Str) ------------------------------------------------------}
  182. {  Returns a string with Str uppercased.                }
  183. {-----------------------------------------------------------------------}
  184.  
  185. function UpStr(Str: String): String;
  186. var
  187.   I: Integer;
  188. begin
  189.   for I := 1 to Length(Str) do
  190.     Str[I] := UpCase(Str[I]);
  191.   UpStr := Str;
  192. end;
  193.  
  194. {----- ReplaceExt(FileName, NExt, Force) -------------------------------}
  195. {  Replace the extension of the given file with the given extension.    }
  196. {  If the an extension already exists Force indicates if it should be   }
  197. {  replaced anyway.                                                     }
  198. {-----------------------------------------------------------------------}
  199.  
  200. function ReplaceExt(FileName: PathStr; NExt: ExtStr; Force: Boolean):
  201.   PathStr;
  202. var
  203.   Dir: DirStr;
  204.   Name: NameStr;
  205.   Ext: ExtStr;
  206. begin
  207.   FileName := UpStr(FileName);
  208.   FSplit(FileName, Dir, Name, Ext);
  209.   if Force or (Ext = '') then
  210.     ReplaceExt := Dir + Name + NExt else
  211.     ReplaceExt := FileName;
  212. end;
  213.  
  214. {----- FExist(FileName) ------------------------------------------------}
  215. {  Returns true if the file exists false otherwise.                     }
  216. {-----------------------------------------------------------------------}
  217.  
  218. function FExists(FileName: PathStr): Boolean;
  219. var
  220.   F: file;
  221.   Attr: Word;
  222. begin
  223.   Assign(F, FileName);
  224.   GetFAttr(F, Attr);
  225.   FExists := DosError = 0;
  226. end;
  227.  
  228.  
  229. {======================== Line Management ==============================}
  230.  
  231. {----- GetLine(S) ------------------------------------------------------}
  232. {  Return the next line out of the stream.                              }
  233. {-----------------------------------------------------------------------}
  234.  
  235. const
  236.   Line: String = '';
  237.   LineInBuffer: Boolean = False;
  238.   Count: Integer = 0;
  239.  
  240. function GetLine(var S: TStream): String;
  241. var
  242.   C, I: Byte;
  243. begin
  244.   if S.Status <> stOk then
  245.   begin
  246.     GetLine := #26;
  247.     Exit;
  248.   end;
  249.   if not LineInBuffer then
  250.   begin
  251.     Line := '';
  252.     C := 0;
  253.     I := 0;
  254.     while (Line[I] <> #13) and (I < 254) and (S.Status = stOk) do
  255.     begin
  256.       Inc(I);
  257.       S.Read(Line[I], 1);
  258.     end;
  259.     Dec(I);
  260.     S.Read(C, 1); { Skip #10 }
  261.     Line[0] := Char(I);
  262.   end;
  263.   Inc(Count);
  264.  
  265.   { Return a blank line if the line is a comment }
  266.   if Line[1] = ';' then Line[0] := #0;
  267.  
  268.   GetLine := Line;
  269.   LineInBuffer := False;
  270. end;
  271.  
  272. {----- UnGetLine(S) ----------------------------------------------------}
  273. {  Return given line into the stream.                                   }
  274. {-----------------------------------------------------------------------}
  275.  
  276. procedure UnGetLine(S: String);
  277. begin
  278.   Line := S;
  279.   LineInBuffer := True;
  280.   Dec(Count);
  281. end;
  282.  
  283. {========================= Error routines ==============================}
  284.  
  285. {----- PrntMsg(Text) ---------------------------------------------------}
  286. {  Used by Error and Warning to print the message.                      }
  287. {-----------------------------------------------------------------------}
  288.  
  289. procedure PrntMsg(Pref: String; var Text: String);
  290. const
  291.   Blank: String[1] = '';
  292. var
  293.   S: String;
  294.   L: array[0..3] of LongInt;
  295. begin
  296.   L[0] := LongInt(@Pref);
  297.  
  298.   L[1] := LongInt(@TextStrm.FileName);        { BUG! fix file name }
  299.  
  300.   L[2] := Count;
  301.   L[3] := LongInt(@Text);
  302.   if Count > 0 then FormatStr(S, '%s: %s(%d): %s'#13#10, L)
  303.   else FormatStr(S, '%s: %s %3#%s', L);
  304.   PrintStr(S);
  305. end;
  306.  
  307. {----- Error(Text) -----------------------------------------------------}
  308. {  Used to indicate an error.  Terminates the program                   }
  309. {-----------------------------------------------------------------------}
  310.  
  311. procedure Error(Text: String);
  312. begin
  313.   PrntMsg('Error', Text);
  314.   Halt(1);
  315. end;
  316.  
  317. {----- Warning(Text) ---------------------------------------------------}
  318. {  Used to indicate an warning.                                         }
  319. {-----------------------------------------------------------------------}
  320.  
  321. procedure Warning(Text: String);
  322. begin
  323.   PrntMsg('Warning', Text);
  324. end;
  325.  
  326. {================ Built-in help context number managment ===============}
  327.  
  328. type
  329.   TBuiltInContext = record
  330.     Text: PChar;
  331.     Number: Word;
  332.   end;
  333.  
  334. { A list of all the help contexts defined in APP }
  335. const
  336.   BuiltInContextTable: array[0..21] of TBuiltInContext = (
  337.     (Text: 'Cascade';   Number: $FF21),
  338.     (Text: 'ChangeDir'; Number: $FF06),
  339.     (Text: 'Clear';     Number: $FF14),
  340.     (Text: 'Close';     Number: $FF27),
  341.     (Text: 'CloseAll';  Number: $FF22),
  342.     (Text: 'Copy';      Number: $FF12),
  343.     (Text: 'Cut';       Number: $FF11),
  344.     (Text: 'DosShell';  Number: $FF07),
  345.     (Text: 'Dragging';  Number: 1),
  346.     (Text: 'Exit';      Number: $FF08),
  347.     (Text: 'New';       Number: $FF01),
  348.     (Text: 'Next';      Number: $FF25),
  349.     (Text: 'Open';      Number: $FF02),
  350.     (Text: 'Paste';     Number: $FF13),
  351.     (Text: 'Prev';      Number: $FF26),
  352.     (Text: 'Resize';    Number: $FF23),
  353.     (Text: 'Save';      Number: $FF03),
  354.     (Text: 'SaveAll';   Number: $FF05),
  355.     (Text: 'SaveAs';    Number: $FF04),
  356.     (Text: 'Tile';      Number: $FF20),
  357.     (Text: 'Undo';      Number: $FF10),
  358.     (Text: 'Zoom';      Number: $FF24)
  359.     );
  360.  
  361. function IsBuiltInContext(Text: String; var Number: Word): Boolean;
  362. var
  363.   Hi, Lo, Mid, Cmp: Integer;
  364. begin
  365.   { Convert Text into a #0 terminted PChar }
  366.   Inc(Text[0]);
  367.   Text[Length(Text)] := #0;
  368.  
  369.   Hi := High(BuiltInContextTable);
  370.   Lo := Low(BuiltInContextTable);
  371.   while Lo <= Hi do
  372.   begin
  373.     Mid := (Hi + Lo) div 2;
  374.     Cmp := StrComp(@Text[1], BuiltInContextTable[Mid].Text);
  375.     if Cmp > 0 then
  376.       Lo := Mid + 1
  377.     else if Cmp < 0 then
  378.       Hi := Mid - 1
  379.     else
  380.     begin
  381.       Number := BuiltInContextTable[Mid].Number;
  382.       IsBuiltInContext := True;
  383.       Exit;
  384.     end;
  385.   end;
  386.   IsBuiltInContext := False;
  387. end;
  388.  
  389. {====================== Topic Reference Management =====================}
  390.  
  391. type
  392.   PFixUp = ^TFixUp;
  393.   TFixUp = record
  394.     Pos: LongInt;
  395.     LineNo: Word;                       { fix unresolved msg }
  396.     Next: PFixUp;
  397.   end;
  398.  
  399.   PReference = ^TReference;
  400.   TReference = record
  401.     Topic: PString;
  402.     case Resolved: Boolean of
  403.       True:  (Value: Word;
  404.               LineNo: Word);            { fix redefinition msg }
  405.       False: (FixUpList: PFixUp);
  406.   end;
  407.  
  408.   PRefTable = ^TRefTable;
  409.   TRefTable = object(TSortedCollection)
  410.     function Compare(Key1, Key2: Pointer): Integer; virtual;
  411.     procedure FreeItem(Item: Pointer); virtual;
  412.     function GetReference(var Topic: String): PReference;
  413.     function KeyOf(Item: Pointer): Pointer; virtual;
  414.   end;
  415.  
  416. const
  417.   RefTable: PRefTable = nil;
  418.  
  419. procedure DisposeFixUps(P: PFixUp);
  420. var
  421.   Q: PFixUp;
  422. begin
  423.   while P <> nil do
  424.   begin
  425.     Q := P^.Next;
  426.     Dispose(P);
  427.     P := Q;
  428.   end;
  429. end;
  430.  
  431. {----- TRefTable -------------------------------------------------------}
  432. {  TRefTable is a collection of PReference's used as a symbol table.    }
  433. {  If the topic has not been seen, a forward reference is inserted and  }
  434. {  a fix-up list is started.  When the topic is seen all forward        }
  435. {  references are resolved.  If the topic has been seen already the     }
  436. {  value it has is used.                                                }
  437. {-----------------------------------------------------------------------}
  438.  
  439. function TRefTable.Compare(Key1, Key2: Pointer): Integer;
  440. var
  441.   K1,K2: String;
  442. begin
  443.   K1 := UpStr(PString(Key1)^);
  444.   K2 := UpStr(PString(Key2)^);
  445.   if K1 > K2 then Compare := 1
  446.   else if K1 < K2 then Compare := -1
  447.   else Compare := 0;
  448. end;
  449.  
  450. procedure TRefTable.FreeItem(Item: Pointer);
  451. var
  452.   Ref: PReference absolute Item;
  453.   P, Q: PFixUp;
  454. begin
  455.   if not Ref^.Resolved then DisposeFixUps(Ref^.FixUpList);
  456.   DisposeStr(Ref^.Topic);
  457.   Dispose(Ref);
  458. end;
  459.  
  460. function TRefTable.GetReference(var Topic: String): PReference;
  461. var
  462.   Ref: PReference;
  463.   I: Integer;
  464. begin
  465.   if Search(@Topic, I) then
  466.     Ref := At(I)
  467.   else
  468.   begin
  469.     New(Ref);
  470.     Ref^.Topic := NewStr(Topic);
  471.     Ref^.Resolved := False;
  472.     Ref^.FixUpList := nil;
  473.     Insert(Ref);
  474.   end;
  475.   GetReference := Ref;
  476. end;
  477.  
  478. function TRefTable.KeyOf(Item: Pointer): Pointer;
  479. begin
  480.   KeyOf := PReference(Item)^.Topic;
  481. end;
  482.  
  483. {----- InitRefTable ----------------------------------------------------}
  484. {  Make sure the reference table is initialized.                        }
  485. {-----------------------------------------------------------------------}
  486.  
  487. procedure InitRefTable;
  488. begin
  489.   if RefTable = nil then
  490.     RefTable := New(PRefTable, Init(5,5));
  491. end;
  492.  
  493. {----- RecordReference -------------------------------------------------}
  494. {  Record a reference to a topic to the given stream.  This routine     }
  495. {  handles forward references.                                          }
  496. {-----------------------------------------------------------------------}
  497.  
  498. procedure RecordReference(var Topic: String; LineNo: Word; var S: TStream); { fix unresolved msg }
  499. var
  500.   I: Integer;
  501.   Ref: PReference;
  502.   FixUp: PFixUp;
  503. begin
  504.   InitRefTable;
  505.   Ref := RefTable^.GetReference(Topic);
  506.   if Ref^.Resolved then
  507.     S.Write(Ref^.Value, SizeOf(Ref^.Value))
  508.   else
  509.   begin
  510.     New(FixUp);
  511.     FixUp^.Pos := S.GetPos;
  512.     FixUp^.LineNo := LineNo;          { fix unresolved msg }
  513.     I := -1;
  514.     S.Write(I, SizeOf(I));
  515.     FixUp^.Next := Ref^.FixUpList;
  516.     Ref^.FixUpList := FixUp;
  517.   end;
  518. end;
  519.  
  520. {----- ResolveReference ------------------------------------------------}
  521. {  Resolve a reference to a topic to the given stream.  This routine    }
  522. {  handles forward references.                                          }
  523. {-----------------------------------------------------------------------}
  524.  
  525. procedure ResolveReference(var Topic: String; Value, LineNo: Word; var S: TStream);  { fix redefinition msg }
  526. var
  527.   I: Integer;
  528.   Ref: PReference;
  529.  
  530. procedure DoFixUps(P: PFixUp);
  531. var
  532.   Pos: LongInt;
  533. begin
  534.   Pos := S.GetPos;
  535.   while P <> nil do
  536.   begin
  537.     S.Seek(P^.Pos);
  538.     S.Write(Value, SizeOf(Value));
  539.     P := P^.Next;
  540.   end;
  541.   S.Seek(Pos);
  542. end;
  543.  
  544. begin
  545.   InitRefTable;
  546.   Ref := RefTable^.GetReference(Topic);
  547.   if Ref^.Resolved then
  548.   begin                                 { fix redefinition msg }
  549.     Count:=Ref^.LineNo;
  550.     Warning('First definition of ' + Ref^.Topic^);
  551.     Count:=LineNo;
  552.     Error('Redefinition of ' + Ref^.Topic^)
  553.   end
  554.   else
  555.   begin
  556.     DoFixUps(Ref^.FixUpList);
  557.     DisposeFixUps(Ref^.FixUpList);
  558.     Ref^.Resolved := True;
  559.     Ref^.Value := Value;
  560.     Ref^.LineNo := LineNo;               { fix redefinition msg }
  561.   end;
  562. end;
  563.  
  564. {======================== Help file parser =============================}
  565.  
  566. {----- GetWord ---------------------------------------------------------}
  567. {   Extract the next word from the given line at offset I.              }
  568. {-----------------------------------------------------------------------}
  569.  
  570. function GetWord(var Line: String; var I: Integer): String;
  571. var
  572.   J: Integer;
  573. const
  574.   WordChars = ['A'..'Z','a'..'z','0'..'9','_'];
  575.  
  576. procedure SkipWhite;
  577. begin
  578.   while (I <= Length(Line)) and (Line[I] = ' ') or (Line[I] = #8) do
  579.     Inc(I);
  580. end;
  581.  
  582. procedure SkipToNonWord;
  583. begin
  584.   while (I <= Length(Line)) and (Line[I] in WordChars) do Inc(I);
  585. end;
  586.  
  587. begin
  588.   SkipWhite;
  589.   J := I;
  590.   if J > Length(Line) then GetWord := ''
  591.   else
  592.   begin
  593.     Inc(I);
  594.     if Line[J] in WordChars then SkipToNonWord;
  595.     GetWord := Copy(Line, J, I - J);
  596.   end;
  597. end;
  598.  
  599. {----- TopicDefinition -------------------------------------------------}
  600. {  Extracts the next topic definition from the given line at I.         }
  601. {-----------------------------------------------------------------------}
  602.  
  603. type
  604.   PTopicDefinition = ^TTopicDefinition;
  605.   TTopicDefinition = object(TObject)
  606.     Topic: PString;
  607.     Value: Word;
  608.     LineNo: Word;                       { fix redefinition msg }
  609.     Next: PTopicDefinition;
  610.     constructor Init(var ATopic: String; AValue, ALineNo: Word);  { fix redefinition msg }
  611.     destructor Done; virtual;
  612.   end;
  613.  
  614. constructor TTopicDefinition.Init(var ATopic: String; AValue, ALineNo: Word); { fix redefinition msg }
  615. begin
  616.   Topic := NewStr(ATopic);
  617.   Value := AValue;
  618.   LineNo := ALineNo;                   { fix redefinition msg }
  619.   Next := nil;
  620. end;
  621.  
  622. destructor TTopicDefinition.Done;
  623. begin
  624.   DisposeStr(Topic);
  625.   if Next <> nil then Dispose(Next, Done);
  626. end;
  627.  
  628. function TopicDefinition(var Line: String; var I: Integer): PTopicDefinition;
  629. var
  630.   J,K: Integer;
  631.   TopicDef: PTopicDefinition;
  632.   Value: Word;
  633.   Topic, W: String;
  634.   HelpNumber: Word;
  635. const
  636.   HelpCounter: Word = 2; {1 is hcDragging}
  637. begin
  638.   Topic := GetWord(Line, I);
  639.   if Topic = '' then
  640.   begin
  641.     Error('Expected topic definition');
  642.     TopicDefinition := nil;
  643.   end
  644.   else
  645.   begin
  646.     J := I;
  647.     W := GetWord(Line, J);
  648.     if W = '=' then
  649.     begin
  650.       I := J;
  651.       W := GetWord(Line, I);
  652.       Val(W, J, K);
  653.       if K <> 0 then Error('Expected numeric')
  654.       else
  655.       begin
  656.         HelpCounter := J;
  657.         HelpNumber := J;
  658.       end
  659.     end
  660.     else
  661.       if not IsBuiltInContext(Topic, HelpNumber) then
  662.       begin
  663.         Inc(HelpCounter);
  664.         HelpNumber := HelpCounter;
  665.       end;
  666.     TopicDefinition := New(PTopicDefinition, Init(Topic, HelpNumber, Count));  { fix redefinition msg }
  667.   end;
  668. end;
  669.  
  670. {----- TopicDefinitionList----------------------------------------------}
  671. {  Extracts a list of topic definitions from the given line at I.       }
  672. {-----------------------------------------------------------------------}
  673.  
  674. function TopicDefinitionList(var Line: String; var I: Integer):
  675.   PTopicDefinition;
  676. var
  677.   J: Integer;
  678.   W: String;
  679.   TopicList, P: PTopicDefinition;
  680. begin
  681.   J := I;
  682.   TopicList := nil;
  683.   repeat
  684.     I := J;
  685.     P := TopicDefinition(Line, I);
  686.     if P = nil then
  687.     begin
  688.       if TopicList <> nil then Dispose(TopicList, Done);
  689.       TopicDefinitionList := nil;
  690.       Exit;
  691.     end;
  692.     P^.Next := TopicList;
  693.     TopicList := P;
  694.     J := I;
  695.     W := GetWord(Line, J);
  696.   until W <> ',';
  697.   TopicDefinitionList := TopicList;
  698. end;
  699.  
  700. {----- TopicHeader -----------------------------------------------------}
  701. {  Parse a the Topic header                                             }
  702. {-----------------------------------------------------------------------}
  703.  
  704. const
  705.   CommandChar = '.';
  706.  
  707. function TopicHeader(var Line: String): PTopicDefinition;
  708. var
  709.   I,J: Integer;
  710.   W: String;
  711.   TopicDef: PTopicDefinition;
  712.  
  713. begin
  714.   I := 1;
  715.   W := GetWord(Line, I);
  716.   if W <> CommandChar then
  717.   begin
  718.     TopicHeader := nil;
  719.     Exit;
  720.   end;
  721.   W := UpStr(GetWord(Line, I));
  722.   if W = 'TOPIC' then
  723.     TopicHeader := TopicDefinitionList(Line, I)
  724.   else
  725.   begin
  726.     Error('TOPIC expected');
  727.     TopicHeader := nil;
  728.   end;
  729. end;
  730.  
  731. {----- ReadParagraph ---------------------------------------------------}
  732. { Read a paragraph of the screen.  Returns the paragraph or nil if the  }
  733. { paragraph was not found in the given stream.  Searches for cross      }
  734. { references and updates the XRefs variable.                            }
  735. {-----------------------------------------------------------------------}
  736. type
  737.   PCrossRefNode = ^TCrossRefNode;
  738.   TCrossRefNode = record
  739.     Topic: PString;
  740.     Offset: Integer;
  741.     Length: Byte;
  742.     LineNo: Word;                               { fix unresolved msg }
  743.     Next: PCrossRefNode;
  744.   end;
  745. const
  746.   BufferSize = 4096;
  747. var
  748.   Buffer: array[0..BufferSize-1] of Byte;
  749.   Ofs: Integer;
  750.  
  751. function ReadParagraph(var TextFile: TStream; var XRefs: PCrossRefNode;
  752.  var Offset: Integer): PParagraph;
  753. var
  754.   Line: String;
  755.   State: (Undefined, Wrapping, NotWrapping);
  756.   P: PParagraph;
  757.  
  758. procedure CopyToBuffer(var Line: String; Wrapping: Boolean); assembler;
  759. asm
  760.         PUSH    DS
  761.         CLD
  762.         PUSH    DS
  763.         POP     ES
  764.         MOV     DI,OFFSET Buffer
  765.         ADD     DI,Ofs
  766.         LDS     SI,Line
  767.         LODSB
  768.         XOR     AH,AH
  769.         ADD     ES:Ofs,AX
  770.         XCHG    AX,CX
  771.         REP     MOVSB
  772.         XOR     AL,AL
  773.         TEST    Wrapping,1      { Only add a #13, line terminator, if not }
  774.         JE      @@1             { currently wrapping the text. Otherwise  }
  775.         MOV     AL,' '-13       { add a ' '.                              }
  776. @@1:    ADD     AL,13
  777. @@2:    STOSB
  778.         POP     DS
  779.         INC     Ofs
  780. end;
  781.  
  782. procedure AddToBuffer(var Line: String; Wrapping: Boolean);
  783. begin
  784.   if Length(Line) + Ofs > BufferSize - 1 then
  785.     Error('Topic too large.')
  786.   else
  787.     CopyToBuffer(Line, Wrapping);
  788. end;
  789.  
  790. procedure ScanForCrossRefs(var Line: String);
  791. var
  792.   I, BegPos, EndPos, Alias: Integer;
  793. const
  794.   BegXRef = '{';
  795.   EndXRef = '}';
  796.   AliasCh = ':';
  797.  
  798. procedure AddXRef(XRef: String; Offset: Integer; Length: Byte);
  799. var
  800.   P: PCrossRefNode;
  801.   PP: ^PCrossRefNode;
  802. begin
  803.   New(P);
  804.   P^.Topic := NewStr(XRef);
  805.   P^.Offset := Offset;
  806.   P^.Length := Length;
  807.   P^.LineNo := Count;           { fix unresolved msg }
  808.   P^.Next := nil;
  809.   PP := @XRefs;
  810.   while PP^ <> nil do
  811.     PP := @PP^^.Next;
  812.   PP^ := P;
  813. end;
  814.  
  815. procedure ReplaceSpacesWithFF(var Line: String; Start: Integer;
  816.   Length: Byte);
  817. var
  818.   I: Integer;
  819. begin
  820.   for I := Start to Start + Length do
  821.     if Line[I] = ' ' then Line[I] := #$FF;
  822. end;
  823.  
  824. begin
  825.   I := 1;
  826.   repeat
  827.     BegPos := Pos(BegXRef, Copy(Line, I, 255));
  828.     if BegPos = 0 then I := 0
  829.     else
  830.     begin
  831.       Inc(I, BegPos);
  832.       if Line[I] = BegXRef then
  833.       begin
  834.         Delete(Line, I, 1);
  835.         Inc(I);
  836.       end
  837.       else
  838.       begin
  839.         EndPos := Pos(EndXRef, Copy(Line, I, 255));
  840.         if EndPos = 0 then
  841.         begin
  842.           Error('Unterminated topic reference.');
  843.           Inc(I);
  844.         end
  845.         else
  846.         begin
  847.           Alias := Pos(AliasCh, Copy(Line, I, 255));
  848.           if (Alias = 0) or (Alias > EndPos) then
  849.             AddXRef(Copy(Line, I, EndPos - 1), Offset + Ofs + I - 1, EndPos - 1)
  850.           else
  851.           begin
  852.             AddXRef(Copy(Line, I + Alias, EndPos - Alias - 1),
  853.               Offset + Ofs + I - 1, Alias - 1);
  854.             Delete(Line, I + Alias - 1, EndPos - Alias);
  855.             EndPos := Alias;
  856.           end;
  857.           ReplaceSpacesWithFF(Line, I, EndPos-1);
  858.           Delete(Line, I + EndPos - 1, 1);
  859.           Delete(Line, I - 1, 1);
  860.           Inc(I, EndPos - 2);
  861.         end;
  862.       end;
  863.     end;
  864.   until I = 0;
  865. end;
  866.  
  867. function IsEndParagraph: Boolean;
  868. begin
  869.   IsEndParagraph :=
  870.      (Line = '') or
  871.      (Line[1] = CommandChar) or
  872.      (Line = #26) or
  873.      ((Line[1] =  ' ') and (State = Wrapping)) or
  874.      ((Line[1] <> ' ') and (State = NotWrapping));
  875. end;
  876.  
  877. begin
  878.   Ofs := 0;
  879.   ReadParagraph := nil;
  880.   State := Undefined;
  881.   Line := GetLine(TextFile);
  882.   while Line = '' do
  883.   begin
  884.     AddToBuffer(Line, State = Wrapping);
  885.     Line := GetLine(TextFile);
  886.   end;
  887.  
  888.   if IsEndParagraph then
  889.   begin
  890.     ReadParagraph := nil;
  891.     UnGetLine(Line);
  892.     Exit;
  893.   end;
  894.   while not IsEndParagraph do
  895.   begin
  896.     if State = Undefined then
  897.       if Line[1] = ' ' then State := NotWrapping
  898.       else State := Wrapping;
  899.     ScanForCrossRefs(Line);
  900.     AddToBuffer(Line, State = Wrapping);
  901.     Line := GetLine(TextFile);
  902.   end;
  903.   UnGetLine(Line);
  904.   GetMem(P, SizeOf(P^) + Ofs);
  905.   P^.Size := Ofs;
  906.   P^.Wrap := State = Wrapping;
  907.   Move(Buffer, P^.Text, Ofs);
  908.   Inc(Offset, Ofs);
  909.   ReadParagraph := P;
  910. end;
  911.  
  912. {----- ReadTopic -------------------------------------------------------}
  913. { Read a topic from the source file and write it to the help file       }
  914. {-----------------------------------------------------------------------}
  915. var
  916.   XRefs: PCrossRefNode;
  917.  
  918. {$IFDEF RangeFix}     (* TVToys HelpFile special *)
  919. procedure HandleCrossRefs(var S: TStream; XRefValue: RefType); far;    { Int->Word fix }
  920. {$ELSE}
  921. procedure HandleCrossRefs(var S: TStream; XRefValue: Integer); far;
  922. {$ENDIF}
  923. var
  924.   P: PCrossRefNode;
  925. begin
  926.   P := XRefs;
  927.   while XRefValue > 1 do
  928.   begin
  929.     if P <> nil then P := P^.Next;
  930.     Dec(XRefValue);
  931.   end;
  932.   if P <> nil then RecordReference(P^.Topic^, P^.LineNo, S);  { fix unresolved msg }
  933. end;
  934.  
  935. procedure ReadTopic(var TextFile: TStream; var HelpFile: THelpFile);
  936. var
  937.   Line: String;
  938.   P: PParagraph;
  939.   Topic: PHelpTopic;
  940.   TopicDef: PTopicDefinition;
  941.   I, J, Offset: Integer;
  942.   Ref: TCrossRef;
  943.   RefNode: PCrossRefNode;
  944.  
  945. procedure SkipBlankLines(var S: TStream);
  946. var
  947.   Line: String;
  948. begin
  949.   Line := '';
  950.   while Line = '' do
  951.     Line := GetLine(S);
  952.   UnGetLine(Line);
  953. end;
  954.  
  955. function XRefCount: Integer;
  956. var
  957.   I: Integer;
  958.   P: PCrossRefNode;
  959. begin
  960.   I := 0;
  961.   P := XRefs;
  962.   while P <> nil do
  963.   begin
  964.     Inc(I);
  965.     P := P^.Next;
  966.   end;
  967.   XRefCount := I;
  968. end;
  969.  
  970. procedure DisposeXRefs(P: PCrossRefNode);
  971. var
  972.   Q: PCrossRefNode;
  973. begin
  974.   while P <> nil do
  975.   begin
  976.     Q := P;
  977.     P := P^.Next;
  978.     if Q^.Topic <> nil then DisposeStr(Q^.Topic);
  979.     Dispose(Q);
  980.   end;
  981. end;
  982.  
  983. procedure RecordTopicDefinitions(P: PTopicDefinition);
  984. begin
  985.   while P <> nil do
  986.   begin
  987.     ResolveReference(P^.Topic^, P^.Value, P^.LineNo, HelpFile.Stream^);  { fix redefinition msg }
  988.     HelpFile.RecordPositionInIndex(P^.Value);
  989.     P := P^.Next;
  990.   end;
  991. end;
  992.  
  993. begin
  994.   { Get Screen command }
  995.   SkipBlankLines(TextFile);
  996.   Line := GetLine(TextFile);
  997.  
  998.   TopicDef := TopicHeader(Line);
  999.  
  1000.   Topic := New(PHelpTopic, Init);
  1001.  
  1002.   { Read paragraphs }
  1003.   XRefs := nil;
  1004.   Offset := 0;
  1005.   P := ReadParagraph(TextFile, XRefs, Offset);
  1006.   while P <> nil do
  1007.   begin
  1008.     Topic^.AddParagraph(P);
  1009.     P := ReadParagraph(TextFile, XRefs, Offset);
  1010.   end;
  1011.  
  1012.   I := XRefCount;
  1013.   Topic^.SetNumCrossRefs(I);
  1014.   RefNode := XRefs;
  1015.   for J := 1 to I do
  1016.   begin
  1017.     Ref.Offset := RefNode^.Offset;
  1018.     Ref.Length := RefNode^.Length;
  1019.     Ref.Ref := J;
  1020.     Topic^.SetCrossRef(J, Ref);
  1021.     RefNode := RefNode^.Next;
  1022.   end;
  1023.  
  1024.   RecordTopicDefinitions(TopicDef);
  1025.  
  1026.   CrossRefHandler := HandleCrossRefs;
  1027.   HelpFile.PutTopic(Topic);
  1028.  
  1029.   if Topic <> nil then Dispose(Topic, Done);
  1030.   if TopicDef <> nil then Dispose(TopicDef, Done);
  1031.   DisposeXRefs(XRefs);
  1032.  
  1033.   SkipBlankLines(TextFile);
  1034. end;
  1035.  
  1036. {----- WriteSymbFile ---------------------------------------------------}
  1037. { Write the .PAS file containing all screen titles as constants.        }
  1038. {-----------------------------------------------------------------------}
  1039.  
  1040. procedure WriteSymbFile(var SymbFile: TProtectedStream);
  1041. const
  1042.   HeaderText1 =
  1043.     'unit ';
  1044.   HeaderText2 =
  1045.     ';'#13#10 +
  1046.     #13#10 +
  1047.     'interface'#13#10 +
  1048.     #13#10 +
  1049.     'const'#13#10 +
  1050.     #13#10;
  1051.   FooterText =
  1052.     #13#10 +
  1053.     'implementation'#13#10 +
  1054.     #13#10 +
  1055.     'end.'#13#10;
  1056.   Header1: array[1..Length(HeaderText1)] of Char = HeaderText1;
  1057.   Header2: array[1..Length(HeaderText2)] of Char = HeaderText2;
  1058.   Footer: array[1..Length(FooterText)] of Char = FooterText;
  1059. var
  1060.   I : Integer;                  { fix unresolved msg }
  1061.   Dir: DirStr;
  1062.   Name: NameStr;
  1063.   Ext: ExtStr;
  1064.  
  1065. procedure DoWriteSymbol(P: PReference); far;
  1066. var
  1067.   L: array[0..1] of LongInt;
  1068.   Line: String;
  1069.   I: Word;
  1070.   Ref: PFixUp;                  { fix unresolved msg }
  1071. begin
  1072.   if (P^.Resolved) then
  1073.   begin
  1074.     if not IsBuiltInContext(P^.Topic^, I) then
  1075.     begin
  1076.       L[0] := LongInt(P^.Topic);
  1077.       L[1] := P^.Value;
  1078.       FormatStr(Line, '  hc%-20s = %d;'#13#10, L);
  1079.       SymbFile.Write(Line[1], Length(Line));
  1080.     end
  1081.   end
  1082.   else
  1083.   begin                         { fix unresolved msg }
  1084.     Ref:=P^.FixUpList;
  1085.     while Ref<>Nil do
  1086.     begin
  1087.       Count:=Ref^.LineNo;
  1088.       Warning('Unresolved forward reference "' + P^.Topic^ + '"');
  1089.       Ref:=Ref^.Next;
  1090.     end;
  1091.   end;
  1092. end;
  1093.  
  1094. begin
  1095.   SymbFile.Write(Header1, SizeOf(Header1));
  1096.   FSplit(SymbFile.FileName, Dir, Name, Ext);
  1097.   SymbFile.Write(Name[1], Length(Name));
  1098.   SymbFile.Write(Header2, SizeOf(Header2));
  1099.  
  1100.   RefTable^.ForEach(@DoWriteSymbol);
  1101.  
  1102.   SymbFile.Write(Footer, SizeOf(Footer));
  1103. end;
  1104.  
  1105. {----- ProcessText -----------------------------------------------------}
  1106. { Compile the given stream, and output a help file.                     }
  1107. {-----------------------------------------------------------------------}
  1108.  
  1109. procedure ProcessText(var TextFile, HelpFile, SymbFile: TProtectedStream);
  1110. var
  1111.   HelpRez: THelpFile;
  1112. begin
  1113.   HelpRez.Init(@HelpFile);
  1114.   while TextFile.Status = stOk do
  1115.     ReadTopic(TextFile, HelpRez);
  1116.   WriteSymbFile(SymbFile);
  1117.   HelpRez.Done;
  1118. end;
  1119.  
  1120. {========================== Program Block ==========================}
  1121.  
  1122. var
  1123.   TextName,
  1124.   HelpName,
  1125.   SymbName: PathStr;
  1126.  
  1127. procedure ExitClean; far;
  1128. begin
  1129.   { Print a message if an out of memory error encountered }
  1130.   if ExitCode = 201 then
  1131.   begin
  1132.     Writeln('Error: Out of memory.');
  1133.     ErrorAddr := nil;
  1134.     ExitCode := 1;
  1135.   end;
  1136.  
  1137.   { Clean up files }
  1138.   TextStrm.Done;
  1139.   SymbStrm.Done;
  1140. end;
  1141.  
  1142. begin
  1143.   { Banner messages }
  1144.   PrintStr('Help Compiler  Version 1.1a  Copyright (c) 1992 Borland International'#13#10);
  1145.   if ParamCount < 1 then
  1146.   begin
  1147.     PrintStr(
  1148.       #13#10 +
  1149.       '  Syntax:  TVHC <Help text>[.TXT] [<Help file>[.HLP] [<Symbol file>[.PAS]]'#13#10 +
  1150.       #13#10+
  1151.       '     Help text   = Help file source'#13#10 +
  1152.       '     Help file   = Compiled help file'#13#10 +
  1153.       '     Symbol file = A Pascal file containing all the screen names as CONST''s'#13#10);
  1154.     Halt(0);
  1155.   end;
  1156.  
  1157.   { Calculate file names }
  1158.   TextName := ReplaceExt(ParamStr(1), '.TXT', False);
  1159.   if not FExists(TextName) then
  1160.     Error('File "' + TextName + '" not found.');
  1161.   if ParamCount >= 2 then
  1162.     HelpName := ReplaceExt(ParamStr(2), '.HLP', False) else
  1163.     HelpName := ReplaceExt(TextName, '.HLP',  True);
  1164.   if ParamCount >= 3 then
  1165.     SymbName := ReplaceExt(ParamStr(3), '.PAS', False) else
  1166.     SymbName := ReplaceExt(HelpName, '.PAS', True);
  1167.  
  1168.   ExitProc := @ExitClean;
  1169.  
  1170.   RegisterHelpFile;
  1171.  
  1172.   TextStrm.Init(TextName, stOpenRead, 1024);
  1173.   SymbStrm.Init(SymbName, stCreate,   1024);
  1174.   HelpStrm := New(PProtectedStream, Init(HelpName, stCreate, 1024));
  1175.   ProcessText(TextStrm, HelpStrm^, SymbStrm);
  1176. end.
  1177.